package run.yiqi.blog.service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import javax.servlet.http.HttpServletResponse;

import org.activiti.api.process.runtime.ProcessRuntime;
import org.activiti.api.task.runtime.TaskRuntime;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricActivityInstanceQuery;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import lombok.Cleanup;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import run.yiqi.util.StringUtil;
import run.yiqi.util.Util;

@Slf4j
@Service
public class WorkFlowService {
    @Getter @Lazy @Autowired private ProcessEngine processEngine;
    @Getter @Lazy @Autowired private ProcessRuntime processRuntime;
    @Getter @Lazy @Autowired private TaskRuntime taskRuntime;
    @Getter @Lazy @Autowired private HistoryService historyService;
    @Getter @Lazy @Autowired private RepositoryService repositoryService;
    @Getter @Lazy @Autowired private RuntimeService runtimeService;
    @Getter @Lazy @Autowired private TaskService taskService;
    @Getter @Lazy @Autowired private ProcessDiagramGenerator processDiagramGenerator;
    
    //ok
    public ProcessInstance startProcessInstance(String processDefinitionKey, Map<String, Object> variables) {
        ProcessInstance processInstance = null;
        if(variables==null) {
            variables = new HashMap<String, Object>();
        }
        processInstance = this.runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
        return processInstance;
    }
    //ok
    public void deleteProcessInstance(String processInstanceId) {
        this.runtimeService.deleteProcessInstance(processInstanceId, "删除流程: "+processInstanceId);
    }
    //ok
    public List<ProcessInstance> listProcessInstances(String processDefinitionKey, Map<String, Object> variables) {
        List<ProcessInstance> processInstanceList = null;
        
        ProcessInstanceQuery piq = this.runtimeService.createProcessInstanceQuery();
        piq.processDefinitionKey(processDefinitionKey);
        variables.forEach((k,v)->piq.variableValueEquals(k,v));
        
        processInstanceList = piq.list();
        return processInstanceList;
    }
    //ok
    public List<ProcessInstance> listProcessInstances(Map<String, Object> variables) {
        List<ProcessInstance> processInstanceList = null;
        
        ProcessInstanceQuery piq = this.runtimeService.createProcessInstanceQuery();
        variables.forEach((k,v)->piq.variableValueEquals(k,v));
        
        processInstanceList = piq.list();
        return processInstanceList;
    }

    /**
     * 任务认领
     */
    public void claim(String taskId, String userName){
        taskService.claim(taskId, userName);
        log.info("任务认领: {}, 已经被: {} 认领", taskId, userName);
    }
    
    /**
     * 任务审批
     */
    public void complete(String taskId, Map<String, Object> variables){
        if(variables==null) {
            this.taskService.complete(taskId);
            log.info("任务审批, TaskId: {}, Variables: null", taskId);
        } else {
            this.taskService.complete(taskId, variables);
            log.info("任务审批, TaskId: {}, Variables: {}", taskId, variables);
        }
    }
    
    /**
     * 任务审批
     */
    public void complete(String taskId){
        this.complete(taskId, null);
    }
    
    /**
     * 设置Activiti当前的操作用户名称.
     * 设置之后, acitiviti会自动获取这个用户名用于后续的流程和人物操作.
     */
    public void setAuthUser (String username) {
        org.activiti.engine.impl.identity.Authentication.setAuthenticatedUserId(username);
    }
    
    /**
     * 设置Activiti当前的操作用户名称.
     * 设置之后, acitiviti会自动获取这个用户名用于后续的流程和人物操作.
     */
    public void setAuthUser () {
        this.setAuthUser(Util.getCurrentUsername());
    }
    
    //ok 查询正在执行的任务集合
    public List<Task> listActiveTasks(String processDefinitionKey, String asignee, Map<String, Object> variables){
        TaskQuery taskQuery = this.taskService.createTaskQuery();
        
        taskQuery.processDefinitionKey(processDefinitionKey);
        taskQuery.taskAssignee(asignee);
        variables.forEach((k,v)->taskQuery.processVariableValueEquals(k,v));
        
        List<Task> taskList = taskQuery.list();
        return taskList;
        
    }
    
    public List<Task> listActiveTasks(String processInstanceId, String asignee){
        TaskQuery taskQuery = this.taskService.createTaskQuery();
        
        if(processInstanceId !=null) {
            taskQuery.processInstanceId(processInstanceId);
        }
        if(asignee !=null) {
            taskQuery.taskAssignee(asignee);
        }
        
        List<Task> taskList = taskQuery.list();
        return taskList;
        
    }
    
    
    
    
    
    //显示流程图
    public void showImg(String processInstanceId, HttpServletResponse response) {
        // 参数校验
        log.info("查看完整流程图！流程实例ID:{}", processInstanceId);
        if (StringUtil.isEmpty(processInstanceId))
            return;

        // 获取流程实例
        HistoricProcessInstance processInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
        if (processInstance == null) {
            log.error("流程实例ID:{}没查询到流程实例！", processInstanceId);
            return;
        }

        // 根据流程对象获取流程对象模型
        BpmnModel bpmnModel = repositoryService.getBpmnModel(processInstance.getProcessDefinitionId());

        // 查看已执行的节点集合
        // 获取流程历史中已执行节点，并按照节点在流程中执行先后顺序排序
        
        // 构造历史流程查询
        HistoricActivityInstanceQuery historyInstanceQuery = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId);
        // 查询历史节点
        List<HistoricActivityInstance> historicActivityInstanceList = historyInstanceQuery.orderByHistoricActivityInstanceStartTime().asc().list();
        if (historicActivityInstanceList == null || historicActivityInstanceList.size() == 0) {
            log.info("流程实例ID:{}没有历史节点信息！", processInstanceId);
            outputImg(response, bpmnModel, null, null);
            return;
        }
        // 已执行的节点ID集合(将historicActivityInstanceList中元素的activityId字段取出封装到executedActivityIdList)
        List<String> executedActivityIdList = historicActivityInstanceList.stream().map(item -> item.getActivityId()).collect(Collectors.toList());

        // 获取流程走过的线
        // 获取流程定义
        ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) repositoryService.getProcessDefinition(processInstance.getProcessDefinitionId());
        List<String> flowIds = getHighLightedFlows(bpmnModel, processDefinition, historicActivityInstanceList);

        //输出图像，并设置高亮
        outputImg(response, bpmnModel, flowIds, executedActivityIdList);
    }

    /**
     * 输出图像
     * 
     * @param response               响应实体
     * @param bpmnModel              图像对象
     * @param flowIds                已执行的线集合
     * @param executedActivityIdList void 已执行的节点ID集合
     */
    private void outputImg(HttpServletResponse response, BpmnModel bpmnModel, List<String> flowIds, List<String> executedActivityIdList) {
        try {
            @Cleanup
            InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", true, "png");
            // 输出资源内容到相应对象
            byte[] b = new byte[1024];
            int len;
            while ((len = imageStream.read(b, 0, 1024)) != -1) {
                response.getOutputStream().write(b, 0, len);
            }
            response.getOutputStream().flush();
        } catch (Exception e) {
            log.error("流程图输出异常！", e);
        }
    }

    /**
     * 获取流程走过的线
     * @param bpmnModel                 流程对象模型
     * @param processDefinitionEntity   流程定义对象
     * @param historicActivityInstances 历史流程已经执行的节点，并已经按执行的先后顺序排序
     * @return List<String> 流程走过的线
     */
    private List<String> getHighLightedFlows(BpmnModel bpmnModel, ProcessDefinitionEntity processDefinitionEntity,
            List<HistoricActivityInstance> historicActivityInstances) {
        // 用以保存高亮的线flowId
        List<String> highFlows = new ArrayList<String>();
        if (historicActivityInstances == null || historicActivityInstances.size() == 0)
            return highFlows;

        // 遍历历史节点
        for (int i = 0; i < historicActivityInstances.size() - 1; i++) {
            // 取出已执行的节点
            HistoricActivityInstance activityImpl_ = historicActivityInstances.get(i);

            // 用以保存后续开始时间相同的节点
            List<FlowNode> sameStartTimeNodes = new ArrayList<FlowNode>();

            // 获取下一个节点（用于连线）
            FlowNode sameActivityImpl = getNextFlowNode(bpmnModel, historicActivityInstances, i, activityImpl_);

            // 将后面第一个节点放在时间相同节点的集合里
            if (sameActivityImpl != null)
                sameStartTimeNodes.add(sameActivityImpl);

            // 循环后面节点，看是否有与此后继节点开始时间相同的节点，有则添加到后继节点集合
            for (int j = i + 1; j < historicActivityInstances.size() - 1; j++) {
                HistoricActivityInstance activityImpl1 = historicActivityInstances.get(j);// 后续第一个节点
                HistoricActivityInstance activityImpl2 = historicActivityInstances.get(j + 1);// 后续第二个节点
                if (activityImpl1.getStartTime().getTime() != activityImpl2.getStartTime().getTime())
                    break;

                // 如果第一个节点和第二个节点开始时间相同保存
                FlowNode sameActivityImpl2 = (FlowNode) bpmnModel.getMainProcess().getFlowElement(activityImpl2.getActivityId());
                sameStartTimeNodes.add(sameActivityImpl2);
            }

            // 得到节点定义的详细信息
            FlowNode activityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i).getActivityId());
            
            // 取出节点的所有出去的线，对所有的线进行遍历
            List<SequenceFlow> pvmTransitions = activityImpl.getOutgoingFlows();
            for (SequenceFlow pvmTransition : pvmTransitions) {
                // 获取节点
                FlowNode pvmActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(pvmTransition.getTargetRef());

                // 不是后继节点
                if (!sameStartTimeNodes.contains(pvmActivityImpl))
                    continue;

                // 如果取出的线的目标节点存在时间相同的节点里，保存该线的id，进行高亮显示
                highFlows.add(pvmTransition.getId());
            }
        }

        // 返回高亮的线
        return highFlows;
    }

    /**
     * 获取下一个节点信息
     * @param bpmnModel                 流程模型
     * @param historicActivityInstances 历史节点
     * @param i                         当前已经遍历到的历史节点索引（找下一个节点从此节点后）
     * @param activityImpl_             当前遍历到的历史节点实例
     * @return FlowNode 下一个节点信息
     */
    private FlowNode getNextFlowNode(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances,
            int i, HistoricActivityInstance activityImpl_) {
        // 保存后一个节点
        FlowNode sameActivityImpl = null;

        // 如果当前节点不是用户任务节点，则取排序的下一个节点为后续节点
        if (!"userTask".equals(activityImpl_.getActivityType())) {
            // 是最后一个节点，没有下一个节点
            if (i == historicActivityInstances.size())
                return sameActivityImpl;
            // 不是最后一个节点，取下一个节点为后继节点
            sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(i + 1).getActivityId());// 找到紧跟在后面的一个节点
            // 返回
            return sameActivityImpl;
        }

        // 遍历后续节点，获取当前节点后续节点
        for (int k = i + 1; k <= historicActivityInstances.size() - 1; k++) {
            // 后续节点
            HistoricActivityInstance activityImp2_ = historicActivityInstances.get(k);
            // 都是userTask，且主节点与后续节点的开始时间相同，说明不是真实的后继节点
            if ("userTask".equals(activityImp2_.getActivityType()) && activityImpl_.getStartTime().getTime() == activityImp2_.getStartTime().getTime())
                continue;
            // 找到紧跟在后面的一个节点
            sameActivityImpl = (FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstances.get(k).getActivityId());
            break;
        }
        return sameActivityImpl;
    }

}
